IM通信系统 (2)

客户端的启动流程

  • 初始化客户端,设置相应参数,配置线程模型,IO模型以及数据连接读写逻辑
  • connect()方法是异步调用的,借助异步回调机制来实现指数退避重连逻辑

相对于服务端,客户端不需要调用childhandler( )方法,客户端不需要监听新连接的接入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import the.flash.protocol.PacketCodeC;
import the.flash.protocol.request.MessageRequestPacket;
import the.flash.util.LoginUtil;

import java.util.Date;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;

public class NettyClient {
private static final int MAX_RETRY = 5;
private static final String HOST = "127.0.0.1";
private static final int PORT = 8000;


public static void main(String[] args) {
NioEventLoopGroup workerGroup = new NioEventLoopGroup();

//客户端启动的引导类,对应Bio中的socket,负责启动客户端并且连接服务端
Bootstrap bootstrap = new Bootstrap();
bootstrap
//同样的,指定线程模型
.group(workerGroup)
//指定IO模型
.channel(NioSocketChannel.class)
//表示连接超时的时间
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
//这里的参数和服务端配置的参数一致
.option(ChannelOption.SO_KEEPALIVE, true)
.option(ChannelOption.TCP_NODELAY, true)
//给引导类指定一个handler,定义业务处理逻辑
.handler(new ChannelInitializer() {
@Override
public void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new ClientHandler());
}
});

connect(bootstrap, HOST, PORT, MAX_RETRY);
}
//如果网络环境差,则会使用失败重连
private static void connect(Bootstrap bootstrap, String host, int port, int retry) {
//bootsrap的conntect方法中有两个参数,第一个参数是IP,第二个参数是端口号
bootstrap.connect(host, port).addListener(future -> {
if (future.isSuccess()) {
System.out.println(new Date() + ": 连接成功,启动控制台线程……");
Channel channel = ((ChannelFuture) future).channel();
startConsoleThread(channel);
} else if (retry == 0) {
System.err.println("重试次数已用完,放弃连接!");
} else {
// 第几次重连
int order = (MAX_RETRY - retry) + 1;
// 本次重连的间隔
int delay = 1 << order;
System.err.println(new Date() + ": 连接失败,第" + order + "次重连……");
//定时任务
// bootstrap.config().group()返回的是线程模型workergroup,调用schedule方法实现定时任务
bootstrap.config().group().schedule(() -> connect(bootstrap, host, port, retry - 1), delay, TimeUnit
.SECONDS);
}
});
}
}

如果在微服务的体系下,客户端不需要约定好端口,都是服务端启动并注册到微服务注册中心,注册host和port,客户端从注册中心获取服务端的信息即可。

此处使用定时任务逻辑,不使用sleep的原因:

  • sleep()需要处理Interrupted异常
  • 可以交给专门的调度线程去做,主线程不会休眠。
0%